#include "vObject.h"
#include <algorithm>
#include "Atmosphere.h"
#include "TerrainRender.h"
#include "TerrainManager.h"

const auto colorValue = 50.0f;
auto whiteColor = D3DXVECTOR4(colorValue * 0.7f, colorValue * 0.4f, colorValue * 0.5f, 1.0f);
auto redColor = D3DXVECTOR4(colorValue, 0.0f, 0.0f, 1.0f);
auto yellowColor = D3DXVECTOR4(colorValue, colorValue, 0.0f, 1.0f);
auto greenColor = D3DXVECTOR4(0.0, colorValue, 0.0f, 1.0f);
auto blackColor = D3DXVECTOR4(0.0, 0.0, 0.0f, 1.0f);


struct BaseObject
{
	std::string ObjectTypeStr;
	ObjectType ObjectType;
	union
	{
		RunwayLightsObject RunwayLights;
		BeaconArrayObject BeaconArray;
	} ObjectParams;
};

typedef std::vector<BaseObject> BaseObjectsVector;

class ObjectsListDataParser: public ConfigFileBlockParser
{
private:
	void ParseBlock()
	{
		auto size = m_src.size();
		const StringVector& srcArr = m_src;
		for(decltype(size) index = 0; index < size; index++)
		{
			auto src = m_src[index];
			std::string firstToken = PeekFirstStringValue(src);
			auto advanceToNextLine = [=, &src, &firstToken, &index, &size, &srcArr]() mutable 
			{ 
				index++; 
				src = (index < size) ? src = srcArr[index] : "END"; 
				firstToken = PeekFirstStringValue(src);
			};
			BaseObject object;
			object.ObjectTypeStr = firstToken;
			if (firstToken == "RUNWAYLIGHTS")
			{
				object.ObjectType = ObjectType_RunwayLights;
				advanceToNextLine();
				RunwayLightsObject& obj = object.ObjectParams.RunwayLights;
				/*set up default values as per specification */
				ZeroMemory(&obj, sizeof(RunwayLightsObject));
				obj.Count = 40;
				obj.TouchZoneDisplacement = 0;
				obj.TouchZoneLength = 400;
				obj.DecisionDistance = 300;
				obj.ApproachStart = 1000;
				do
				{
					if (firstToken == "END1")
					{
						ParseLine(src, "S00DDD", obj.End1, 1);
					}
					else if (firstToken == "END2")
					{
						ParseLine(src, "S00DDD", obj.End2, 1);
					}
					else if (firstToken == "WIDTH")
					{
						ParseLine(src, "S00D", obj.Width, 1);
					}
					else if (firstToken == "COUNT1")
					{
						ParseLine(src, "S00I", obj.Count, 1);
					}
					else if (firstToken == "PAPI")
					{
						ParseLine(src, "S00DDD", obj.PAPI, 1);
					}
					else if (firstToken == "VASI")
					{
						ParseLine(src, "S00DDD", obj.VASI, 1);
					}
					else if (firstToken == "TD_DISP")
					{
						ParseLine(src, "S00D", obj.TouchZoneDisplacement, 1);
					}
					else if (firstToken == "TD_LENGTH")
					{
						ParseLine(src, "S00D", obj.TouchZoneLength, 1);
					}
					else if (firstToken == "DECISION_DIST")
					{
						ParseLine(src, "S00D", obj.DecisionDistance, 1);
					}
					else if (firstToken == "APPROACH_START")
					{
						ParseLine(src, "S00D", obj.ApproachStart, 1);
					}
					advanceToNextLine();
				} while (firstToken != "END");
			}
			else if (firstToken == "BEACONARRAY")
			{
				object.ObjectType = ObjectType_BeaconArray;
				advanceToNextLine();
				BeaconArrayObject& obj = object.ObjectParams.BeaconArray;
				/*set up default values as per specification */
				obj.Count = 10;
				obj.Size = 1.0;
				obj.Color.x = 1.0;
				obj.Color.y = 1.0;
				obj.Color.z = 1.0;
				ZeroMemory(&obj, sizeof(BeaconArrayObject));
				do
				{
					if (firstToken == "END1")
					{
						ParseLine(src, "S00DDD", obj.End1, 1);
					}
					else if (firstToken == "END2")
					{
						ParseLine(src, "S00DDD", obj.End2, 1);
					}
					else if (firstToken == "COUNT")
					{
						ParseLine(src, "S00I", obj.Count, 1);
					}
					else if (firstToken == "SIZE")
					{
						ParseLine(src, "S00D", obj.Size, 1);
					}
					else if (firstToken == "COL")
					{
						ParseLine(src, "S00DDD", obj.Color, 1);
					}
					advanceToNextLine();
				} while (firstToken != "END");
			}
			else
			{
				object.ObjectType = ObjectType_Other;
				advanceToNextLine();
				do
				{
					advanceToNextLine();
				} while (firstToken != "END");
			}
			m_objects.push_back(object);
		}
	}
	std::vector<BaseObject> m_objects;
public:
	ObjectsListDataParser(const StringVector& src)
		: ConfigFileBlockParser(src)
	{
		ParseBlock();
	}

	void GetObjectByType(ObjectType objType, BaseObjectsVector& result)
	{
		auto size = m_objects.size();
		for(decltype(size) index = 0; index < size; index++)
		{
			BaseObject& object = m_objects[index];
			if (object.ObjectType == objType)
			{
				result.push_back(object);
			}
		}
	}
};

vBase::vBase( vPlanet *_PLN, OBJHANDLE _planet, OBJHANDLE obj, const ConfigFileParser * config, Scene * scene ) 
	: vObject( obj, scene ), m_config(config)
{
	DWORD j;

//	PLN = _PLN;
//	planet = _planet;

	tspec = NULL;
	tilemesh = NULL;
	shadow_mesh = NULL;
	structs_bs = structs_as = NULL;
	nstructs_bs = nstructs_as = nshadow_mesh = 0;
	nshmesh	= 0;
	PLN = _PLN;
	planet = _planet;

	//load surface tiles:
	ntile = gc->GetBaseTileList( obj, &tspec );

	if( ntile ) {
		MESHGROUPEX **grps = new MESHGROUPEX *[ntile];
		SURFHANDLE *texs = new SURFHANDLE [ntile];

		for( j = 0; j < ntile; j++ ) {
			DWORD ng = oapiMeshGroupCount( tspec[j].mesh );
			if( ng == 1 ) {		//tiles should have 1 group
				grps[j] = oapiMeshGroupEx( tspec[j].mesh, 0 );
				texs[j] = tspec[j].tex;
			}
			else
				oapiWriteLog( "Incorrect number of base tile groups" );
		}
		tilemesh = new D3D11Mesh( ntile, grps, texs );
	}

	//load meshes for structures
	MESHHANDLE *sbs, *sas;
	DWORD nsbs, nsas;

	gc->GetBaseStructures( obj, &sbs, &nsbs, &sas, &nsas );

	if( nstructs_bs = nsbs ) {
		structs_bs = new D3D11Mesh *[nstructs_bs];
		for( j = 0; j < nstructs_bs; j++ )
			structs_bs[j] = new D3D11Mesh( sbs[j], true );
	}

	if( nstructs_as = nsas ) {
		structs_as = new D3D11Mesh *[nstructs_as];
		for( j = 0; j < nstructs_as; j++ )
			structs_as[j] = new D3D11Mesh( sas[j], true );
	}

	//"black quad" fix
	if( !strncmp( name, "Cape Canaveral", 14 ) && nstructs_as > 2 && structs_as[2]->ngrp == 12 ) {
		structs_as[2]->GR[11].userflag |= 2;
	}

	SetupShadowMeshes();

	p_night = false;
	//setup lights !

	InitBaseLights();

	InitShaders();
	m_previousSimTime = 0.0;
}

D3D10_SHADER_MACRO defaultConfig[] = 
{
	{ "NO_ATMOSPHERE", "1" },
	{ nullptr, nullptr }
};

void vBase::InitShaders()
{
	auto tileRenderManager = PLN->TerM->GetTileRenderMgr();
	auto shaderConfig = tileRenderManager->GetShaderConfig();
	if (!shaderConfig)
	{
		shaderConfig = defaultConfig;
	}

	GET_SHADER_DEFINES(gc, "Modules\\D3D11Shaders\\Mesh_BaseTile.fx", "PS_BaseTile", m_pixelShader, shaderConfig);

	GET_SHADER(gc, "Modules\\D3D11Shaders\\BaseLights.fx", "VS", m_baseLightsVS);
	GET_SHADER(gc, "Modules\\D3D11Shaders\\BaseLights.fx", "PS", m_baseLightsPS);
}

void vBase::InitTouchZoneBorder(const RunwayLightsObject& obj, const VECTOR3& rwDir, const VECTOR3& tangentDir, bool isFrontEnd)
{
	//touchdown zone border
	//"front" end
	auto touchZoneCenter = isFrontEnd 
		? (obj.End1 + rwDir * obj.TouchZoneDisplacement) 
		: (obj.End2 - rwDir * obj.TouchZoneDisplacement);
	//put 20 lights along the rwy
	for(auto offset = -obj.Width / 2 - 20; offset < obj.Width / 2 + 20; offset+= obj.Width / 20)
	{
		BaseLight light;
		light.forceDisable = false;
		light.Color = isFrontEnd ? greenColor : redColor;
		light.BackColor = !isFrontEnd ? greenColor : redColor;;
		light.Dir = rwDir;
		light.Pos = touchZoneCenter + tangentDir * offset;
		light.RenderFlags = BASE_LIGHT_RENDER_ALWAYS;
		m_baseLights.push_back(light);
	}
}

void vBase::InitTouchZoneMarkings(const RunwayLightsObject& obj, const VECTOR3& rwDir, const VECTOR3& tangentDir, 
	const VECTOR3& frontOffset, const VECTOR3& backOffset, bool isFrontEnd)
{
	//series of strip of 4 white lights along touchdown zone, placed every 30m 
	for(double currentDistance = 1.0; currentDistance < obj.TouchZoneLength; currentDistance += 30)
	{
		auto currentTouchzoneCenter = isFrontEnd 
			? frontOffset + rwDir * currentDistance
			: backOffset - rwDir * currentDistance;
		BaseLight light;
		light.forceDisable = false;
		light.Color = light.BackColor = whiteColor;
		light.Dir = rwDir;
		light.RenderFlags = isFrontEnd ? BASE_LIGHT_RENDER_FRONT : BASE_LIGHT_RENDER_BACK;
		for(auto num = 0; num < 4; num++)
		{
			light.Pos = currentTouchzoneCenter - tangentDir * (num * 2 + obj.Width / 4);
			m_baseLights.push_back(light);
			light.Pos = currentTouchzoneCenter + tangentDir * (num * 2 + obj.Width / 4);
			m_baseLights.push_back(light);
		}
	}
	//pre-touch zone
	//series of strip of 4 red lights from decision distance to start of touchdown zone, placed every 30m 
	for(double currentDistance = 1.0; currentDistance < obj.DecisionDistance; currentDistance += 30)
	{
		auto currentTouchzoneCenter = isFrontEnd 
			? frontOffset - rwDir * currentDistance
			: backOffset + rwDir * currentDistance;
		BaseLight light;
		light.forceDisable = false;
		light.Color = light.BackColor = redColor;
		light.Dir = rwDir;
		light.RenderFlags = isFrontEnd ? BASE_LIGHT_RENDER_FRONT : BASE_LIGHT_RENDER_BACK;
		for(auto num = 0; num < 4; num++)
		{
			light.Pos = currentTouchzoneCenter - tangentDir * (num * 2 + obj.Width / 4);
			m_baseLights.push_back(light);
			light.Pos = currentTouchzoneCenter + tangentDir * (num * 2 + obj.Width / 4);
			m_baseLights.push_back(light);
		}
	}
}	

void vBase::InitApproachZoneMarkings(const RunwayLightsObject& obj, const VECTOR3& rwDir, const VECTOR3& tangentDir, 
	const VECTOR3& frontOffset, const VECTOR3& backOffset, bool isFrontEnd)
{
	RunningLightsSystem runningLightsVector;
	//series of strips of 7 white lights along center line's continuation 10 times until decision distance, afterwards - every 60m
	for(double currentDistance = obj.DecisionDistance / 10; currentDistance < obj.DecisionDistance; currentDistance += obj.DecisionDistance / 10)
	{
		auto currentCenter = isFrontEnd 
			? frontOffset - rwDir * currentDistance
			: backOffset + rwDir * currentDistance;
		BaseLight light;
		light.forceDisable = false;
		light.Color = light.BackColor = whiteColor;
		light.Dir = rwDir;
		light.RenderFlags = isFrontEnd ? BASE_LIGHT_RENDER_FRONT : BASE_LIGHT_RENDER_BACK;

		for(auto num = -3; num < 4; num++)
		{
			light.Pos = currentCenter + tangentDir * (num * 2);
			m_baseLights.push_back(light);
		}
	}
	//decision distance
	{
		auto currentCenter = isFrontEnd 
			? frontOffset - rwDir * obj.DecisionDistance
			: backOffset + rwDir * obj.DecisionDistance;
		BaseLight light;
		light.forceDisable = false;
		light.Color = light.BackColor = whiteColor;
		light.Dir = rwDir;
		light.RenderFlags = isFrontEnd ? BASE_LIGHT_RENDER_FRONT : BASE_LIGHT_RENDER_BACK;
		//center part
		for(auto num = -3; num < 4; num++)
		{
			light.Pos = currentCenter + tangentDir * (num * 2);
			m_baseLights.push_back(light);
		}
		//left part
		for(auto num = 0; num < 10; num++)
		{
			light.Pos = currentCenter + tangentDir * (-obj.Width / 2 + num * 1.5 );
			m_baseLights.push_back(light);
		}
		//right part
		for(auto num = 0; num < 10; num++)
		{
			light.Pos = currentCenter + tangentDir * (obj.Width / 2 - num * 1.5);
			m_baseLights.push_back(light);
		}
	}
	//beyond decision until approach start
	for(double currentDistance = obj.DecisionDistance; currentDistance < obj.ApproachStart; currentDistance += 60)
	{
		auto currentCenter = isFrontEnd 
			? frontOffset - rwDir * currentDistance
			: backOffset + rwDir * currentDistance;
		BaseLight light;
		light.forceDisable = false;
		light.Color = light.BackColor = whiteColor;
		light.Dir = rwDir;
		light.RenderFlags = isFrontEnd ? BASE_LIGHT_RENDER_FRONT : BASE_LIGHT_RENDER_BACK;
		BaseLightsGroupVector group;
		for(auto num = -3; num < 4; num++)
		{
			light.Pos = currentCenter + tangentDir * (num * 2);
			m_baseLights.push_back(light);
			group.push_back(m_baseLights.size() - 1);
		}
		runningLightsVector.lightGroups.push_back(group);
	}
	std::reverse(runningLightsVector.lightGroups.begin(), runningLightsVector.lightGroups.end());
	runningLightsVector.disabledGroupIndex = (UINT)-1;
	m_runningLightsSystems.push_back(runningLightsVector);
}

void vBase::InitPAPILights(const RunwayLightsObject& obj, const VECTOR3& rwDir, const VECTOR3& tangentDir, bool isFrontEnd)
{
	if (length(obj.PAPI) == 0)
		//there is no PAPI
		return;
	auto designatedApproachAngle = obj.PAPI.x;
	auto approachConeAperture = obj.PAPI.y;
	auto papiOffset = obj.PAPI.z;
	auto papiCenter = isFrontEnd 
		? (obj.End1 + rwDir * papiOffset + tangentDir * obj.Width) 
		: (obj.End2 - rwDir * papiOffset - tangentDir * obj.Width);
	PAPILightsSystem system;
	system.ApproachDirection = isFrontEnd ? rwDir : -rwDir;
	system.AboveColor = whiteColor;
	system.BelowColor = redColor;
	auto distanceBetweenLights = obj.Width / 5;
	for(auto i = 0; i < 4; i++)
	{
		PAPILight & light = system.Lights[i];
		light.angle = designatedApproachAngle + approachConeAperture * (i - 1.5);
		light.Position = papiCenter + tangentDir * distanceBetweenLights * (i - 1.5);
	}
	m_papiLights.push_back(system);
}

void vBase::InitVASILights(const RunwayLightsObject& obj, const VECTOR3& rwDir, const VECTOR3& tangentDir, bool isFrontEnd)
{
	if (length(obj.VASI) == 0)
		//there is no VASI
		return;
	auto designatedApproachAngle = obj.VASI.x;
	auto distance = obj.VASI.y;
	auto vasiOffset = obj.VASI.z;
	auto vasiCenter = isFrontEnd 
		? (obj.End1 + rwDir * vasiOffset + tangentDir * obj.Width) 
		: (obj.End2 - rwDir * vasiOffset - tangentDir * obj.Width);
	VASILightsSystem system;
	system.ApproachDirection = isFrontEnd ? rwDir : -rwDir;
	system.AboveColor = whiteColor;
	system.BelowColor = redColor;
	//first set of lights
	for(auto i = 0; i < 4; i++)
	{
		VASILight & light = system.Lights[i];
		light.angle = designatedApproachAngle;
		light.Position = vasiCenter + tangentDir * 10 * (i - 1.5);
	}
	vasiCenter = isFrontEnd 
		? vasiCenter + rwDir * distance
		: vasiCenter - rwDir * distance;
	for(auto i = 4; i < 8; i++)
	{
		VASILight & light = system.Lights[i];
		light.angle = designatedApproachAngle;
		light.Position = vasiCenter + tangentDir * 10 * (i - 4 - 1.5);
	}
	m_vasiLights.push_back(system);
}

void vBase::InitBaseLights()
{
	if (m_config == nullptr)
	{
		return;
	}
	auto block = m_config->GetBlock<ObjectsListDataParser>("OBJECTLIST");
	if (block == nullptr)
	{
		return;
			
	}
	BaseObjectsVector runwayLights;
	block->GetObjectByType(ObjectType_RunwayLights, runwayLights);
	if (runwayLights.size() == 0)
	{
		delete block;
		return;
	}
	for(auto it = runwayLights.begin(); it != runwayLights.end(); it++)
	{
		const RunwayLightsObject& obj = it->ObjectParams.RunwayLights;
		//caclulate direction from beginning to end
		auto direction = obj.End2 - obj.End1;
		auto stepsCount = obj.Count * 2/* just for fun :)*/ - 1;
		if (stepsCount < 1)
		{
			stepsCount = 1;
		}
		auto distance = length(direction);
		
		VECTOR3 upVector = { 0, 1, 0};
		auto normalizedWidthDir = crossp(direction, upVector);
		normalise(normalizedWidthDir);
		VECTOR3 rwDir = direction;
		normalise(rwDir);

		// lighting spec was taken from here: http://en.wikipedia.org/wiki/Runway and faa.gov
		InitPAPILights(obj, rwDir, normalizedWidthDir, true);
		InitPAPILights(obj, rwDir, normalizedWidthDir, false);

		InitVASILights(obj, rwDir, normalizedWidthDir, true);
		InitVASILights(obj, rwDir, normalizedWidthDir, false);

		//touchdown zone border
		InitTouchZoneBorder(obj, rwDir, normalizedWidthDir, true);
		InitTouchZoneBorder(obj, rwDir, normalizedWidthDir, false);

		auto offsetStart = obj.End1 + rwDir * obj.TouchZoneDisplacement;
		auto offsetEnd = obj.End2 - rwDir * obj.TouchZoneDisplacement;

		//touch zone markings
		InitTouchZoneMarkings(obj, rwDir, normalizedWidthDir, offsetStart, offsetEnd, true);
		InitTouchZoneMarkings(obj, rwDir, normalizedWidthDir, offsetStart, offsetEnd, false);
		
		//approach lights
		InitApproachZoneMarkings(obj, rwDir, normalizedWidthDir, offsetStart, offsetEnd, true);
		InitApproachZoneMarkings(obj, rwDir, normalizedWidthDir, offsetStart, offsetEnd, false);
		
		auto step = (offsetEnd - offsetStart) / stepsCount;
		auto stepDistance = length(step);
		auto currentPosition = offsetStart;
		auto currentDistance = 0.0;
		
		auto widthDir = normalizedWidthDir * obj.Width / 2;
		bool firstAlt = true;

		for(int i = 0; i < stepsCount; i++)
		{
			BaseLight light;
			light.forceDisable = false;
			light.Pos = currentPosition;
			light.Dir = rwDir;
			auto closeToBeginning = (currentDistance < (distance / 2));
			auto distanceFromEdge = (currentDistance > (distance / 2)) ? (distance - currentDistance) : currentDistance;
			if (distanceFromEdge > 914)
			{
				//always white
				light.Color = whiteColor;
				light.BackColor = whiteColor;
			}
			else if (distanceFromEdge > 305)
			{
				//alternate white and red
				if (closeToBeginning)
				{
					//front is always white
					light.Color = whiteColor;
					//back is alternate white and red
					light.BackColor = firstAlt ? redColor : whiteColor;
					firstAlt = !firstAlt;
				}
				else
				{
					//front is always white
					light.BackColor = whiteColor;
					//back is alternate white and red
					light.Color = firstAlt ? redColor : whiteColor;
					firstAlt = !firstAlt;
				}
			}
			else
			{
				//it's close to edge, so has to be red when seen from center of the RWY
				light.Color = closeToBeginning ? whiteColor : redColor;
				light.BackColor = !closeToBeginning ? whiteColor : redColor;
			}
			light.RenderFlags = BASE_LIGHT_RENDER_ALWAYS;
			m_baseLights.push_back(light);

			light.Pos = currentPosition - widthDir;
			if (distanceFromEdge > 610)
			{
				light.Color = whiteColor;
				light.BackColor = whiteColor;
			}
			else
			{
				//it's close to edge, so has to be yellow when seen from center of the RWY
				light.Color = closeToBeginning ? whiteColor : yellowColor;
				light.BackColor = !closeToBeginning ? whiteColor : yellowColor;
			}
			light.RenderFlags = BASE_LIGHT_RENDER_ALWAYS;
			m_baseLights.push_back(light);

			light.Pos = currentPosition + widthDir;
			if (distanceFromEdge > 610)
			{
				light.Color = whiteColor;
				light.BackColor = whiteColor;
			}
			else
			{
				//it's close to edge, so has to be yellow when seen from center of the RWY
				light.Color = closeToBeginning ? whiteColor : yellowColor;
				light.BackColor = !closeToBeginning ? whiteColor : yellowColor;
			}
			light.RenderFlags = BASE_LIGHT_RENDER_ALWAYS;
			m_baseLights.push_back(light);

			currentPosition += step;
			currentDistance += stepDistance;
		}
	}
}

vBase::~vBase() {
	if( tilemesh )
		delete tilemesh;
	if( structs_bs ) {
		for( DWORD j = 0; j < nstructs_bs; j++ )
			delete structs_bs[j];
		delete [ ] structs_bs;
	}
	if( structs_as ) {
		for( DWORD j = 0; j < nstructs_as; j++ )
			delete structs_as[j];
		delete [ ] structs_as;
	}
	if( shadow_mesh ) {
		for( DWORD j = 0; j < nshmesh; j++ ) {
			REL( shadow_mesh[j].VB );
			REL( shadow_mesh[j].IB );
		}
		delete [ ] shadow_mesh;
	}
}

void vBase::InitRenderBaseLights()
{
	const UINT BBvertexStride = 20, VBOffset = 0;
	iCtx->IASetInputLayout( vStar::IL_BBVertex );
	iCtx->IASetIndexBuffer(nullptr, DXGI_FORMAT_R32_UINT, 0);
	iCtx->IASetVertexBuffers( 0, 1, &vStar::VB, &BBvertexStride, &VBOffset );
	iCtx->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );

	/*
	IL_BillBoard = vStar::IL_BBVertex;
	VS_BillBoard = vStar::VS;
	PS_BillBoard = vStar::PS;
	*/

	iCtx->VSSetConstantBuffers( 0, 1, &cb_D3DXMATRIX_x1 );
	
	iCtx->PSSetConstantBuffers( 0, 1, &cb_D3DXVECTOR4 );
	iCtx->PSSetSamplers( 0, 1, &SS_Linear_Wrap );

	iCtx->OMSetBlendState( BS_SrcAlpha, D3DXCOLOR( 1, 1, 1, 1 ), 0xFFFFFFFF );
	iCtx->OMSetDepthStencilState( DSS_TestDepthOnly_NoStencil, 0 );
	
	iCtx->PSSetShaderResources( 0, 1, vVessel::Ball[2]->GetSRV() );
}

void vBase::RenderBaseLights()
{
	iCtx->VSSetShader( m_baseLightsVS, NULL, NULL );
	iCtx->PSSetShader( m_baseLightsPS, NULL, NULL );

	auto size = m_baseLights.size();
	if (p_night)
	{
		//display all lights but VASI and PAPI only during nighttime
		for(decltype(size) i = 0; i < size; i++)
		{
			const BaseLight& light = m_baseLights[i];
			if (light.forceDisable)
				continue;
			//render spot
			VECTOR3 cpos( CPos );
			cpos += mul( mROT, light.Pos );

			VECTOR3 cameraDir(cpos);
			auto cameraDistance = length(cameraDir);
			if (cameraDistance > 50000)
			{
				//do not display lights that are more than 50 kms away
				continue;
			}
			auto lightSize = 2.0 + cameraDistance / 1000.0;
			normalise(cameraDir);
		
			D3DXVECTOR4 color = light.Color; 
			VECTOR3 rwyDir(light.Dir);
			rwyDir = mul(mROT, rwyDir);
			normalise(rwyDir);
			bool isFrontFacing = dotp(cameraDir, rwyDir) > 0;
			if ((isFrontFacing && ((light.RenderFlags & BASE_LIGHT_RENDER_FRONT) == 0))
				|| (!isFrontFacing && ((light.RenderFlags & BASE_LIGHT_RENDER_BACK) == 0)))
			{
				continue;
			}
			if (!isFrontFacing)
			{
				color = light.BackColor; 
			}

			D3DXMATRIX W;
			D3DXVECTOR3 vPos( (float)cpos.x, (float)cpos.y, (float)cpos.z ), vCam;

			D3DXVec3Normalize( &vCam, &vPos );
			D3DMAT_CreateX_Billboard( &vCam, &vPos, (float)lightSize, &W );

			//render spot...
			D3DXMATRIX M = W * *SC->GetVP();
			D3DXVECTOR4 ex = D3DXVECTOR4( 0, -1, 1, 1 );
			D3DXVECTOR4 ex0;
			D3DXVec4Transform( &ex0, &ex, &M );
			iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, &M, 0, 0 );
			iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, color, 0, 0 );


			iCtx->Draw( 4, 0 );
		}
	}
	//VASI and PAPI are always displayed regardless of time of the day
	VECTOR3 upVector = {0.0, 1.0, 0.0 };
	upVector = mul(mROT, upVector);
	normalise(upVector);
	size = m_papiLights.size();
	for(decltype(size) i = 0; i < size; i++)
	{
		const PAPILightsSystem& system = m_papiLights[i];
		
		VECTOR3 rwyDir(system.ApproachDirection);
		rwyDir = mul(mROT, rwyDir);
		normalise(rwyDir);
		for (auto i = 0; i < 4; i++)
		{
			const PAPILight& light = system.Lights[i];
			//render spot
			VECTOR3 cpos( CPos );
			cpos += mul( mROT, light.Position );

			VECTOR3 cameraDir(cpos);
			auto cameraDistance = length(cameraDir);
			if (cameraDistance > 50000)
			{
				//do not display lights that are more than 50 kms away
				continue;
			}
			auto lightSize = 6.0 + cameraDistance / 1000.0;
			normalise(cameraDir);
		
			auto rwProjection = dotp(cameraDir, rwyDir);
			bool isFrontFacing = rwProjection > 0;
			if (!isFrontFacing)
			{
				continue;
			}
			auto upProjection = -dotp(cameraDir, upVector);
			VECTOR3 cameraDirProjectionToRwyPlane = rwyDir * rwProjection + upVector * upProjection;
			normalise(cameraDirProjectionToRwyPlane);
			auto angle = (180.0 / PI) * (acos(dotp(cameraDirProjectionToRwyPlane, rwyDir)));
			D3DXVECTOR4 color = angle > light.angle ? system.AboveColor : system.BelowColor;
			

			D3DXMATRIX W;
			D3DXVECTOR3 vPos( (float)cpos.x, (float)cpos.y, (float)cpos.z ), vCam;

			D3DXVec3Normalize( &vCam, &vPos );
			D3DMAT_CreateX_Billboard( &vCam, &vPos, (float)lightSize, &W );

			//render spot...
			D3DXMATRIX M = W * *SC->GetVP();
			D3DXVECTOR4 ex = D3DXVECTOR4( 0, -1, 1, 1 );
			D3DXVECTOR4 ex0;
			D3DXVec4Transform( &ex0, &ex, &M );
			iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, &M, 0, 0 );
			iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, color, 0, 0 );


			iCtx->Draw( 4, 0 );
		}
	}
	size = m_vasiLights.size();
	for(decltype(size) i = 0; i < size; i++)
	{
		const VASILightsSystem& system = m_vasiLights[i];
		
		VECTOR3 rwyDir(system.ApproachDirection);
		rwyDir = mul(mROT, rwyDir);
		normalise(rwyDir);
		for (auto i = 0; i < 8; i++)
		{
			const VASILight& light = system.Lights[i];
			//render spot
			VECTOR3 cpos( CPos );
			cpos += mul( mROT, light.Position );

			VECTOR3 cameraDir(cpos);
			auto cameraDistance = length(cameraDir);
			if (cameraDistance > 50000)
			{
				//do not display lights that are more than 50 kms away
				continue;
			}
			auto lightSize = 4.0 + cameraDistance / 1000.0;
			normalise(cameraDir);
		
			auto rwProjection = dotp(cameraDir, rwyDir);
			bool isFrontFacing = rwProjection > 0;
			if (!isFrontFacing)
			{
				continue;
			}
			auto upProjection = -dotp(cameraDir, upVector);
			VECTOR3 cameraDirProjectionToRwyPlane = rwyDir * rwProjection + upVector * upProjection;
			normalise(cameraDirProjectionToRwyPlane);
			auto angle = (180.0 / PI) * (acos(dotp(cameraDirProjectionToRwyPlane, rwyDir)));
			D3DXVECTOR4 color = angle > light.angle ? system.AboveColor : system.BelowColor;
			

			D3DXMATRIX W;
			D3DXVECTOR3 vPos( (float)cpos.x, (float)cpos.y, (float)cpos.z ), vCam;

			D3DXVec3Normalize( &vCam, &vPos );
			D3DMAT_CreateX_Billboard( &vCam, &vPos, (float)lightSize, &W );

			//render spot...
			D3DXMATRIX M = W * *SC->GetVP();
			D3DXVECTOR4 ex = D3DXVECTOR4( 0, -1, 1, 1 );
			D3DXVECTOR4 ex0;
			D3DXVec4Transform( &ex0, &ex, &M );
			iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, &M, 0, 0 );
			iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, color, 0, 0 );


			iCtx->Draw( 4, 0 );
		}
	}
}

void vBase::SetupShadowMeshes() {
	nshmesh = 0;

	//get base shadow geometry
	DWORD ngrp, nssh, j, i, k, m, nmesh;
	MESHHANDLE *ssh;
	double *ecorr;

	gc->GetBaseShadowGeometry( obj, &ssh, &ecorr, &nssh );

	if( !nssh )
		return;

	struct EGROUP {
		MESHHANDLE *mesh;
		DWORD
			nmesh,
			nvtx,
			nidx;
		int bin;
	} *eg;

	const double d_ecorr = 0.2;

	for( i = 0; i < nssh; i++ ) {
		int bin = (int)(ecorr[i]/d_ecorr);

		for( j = 0; j < nshmesh; j++ )
			if( bin == eg[j].bin )
				break;

		if( j == nshmesh ) {
			EGROUP *tmp = new EGROUP [nshmesh+1];
			if( nshmesh ) {
				memcpy( tmp, eg, sizeof(EGROUP)*nshmesh );
				delete [ ] eg;
			}
			eg = tmp;
			eg[nshmesh].nmesh = eg[nshmesh].nvtx = eg[nshmesh].nidx = 0;
			eg[nshmesh].bin = bin;
			nshmesh++;
		}

		nmesh = eg[j].nmesh;
		MESHHANDLE *tmp = new MESHHANDLE [nmesh+1];
		if( nmesh ) {
			memcpy( tmp, eg[j].mesh, sizeof(MESHHANDLE)*nmesh );
			delete [ ] eg[j].mesh;
		}
		eg[j].mesh = tmp;
		eg[j].mesh[nmesh] = ssh[i];
		ngrp = oapiMeshGroupCount( ssh[i] );
		for( k = 0; k < ngrp; k++ ) {
			MESHGROUP *grp = oapiMeshGroup( ssh[i], k );
			if( grp ) {
			//	if( grp->UsrFlag & 1 )	//"no shadows" flag
			//		continue;
				eg[j].nvtx += grp->nVtx;
				eg[j].nidx += grp->nIdx;
			}
		}
		eg[j].nmesh++;
	}

	shadow_mesh = new ShadowMesh [nshmesh];

	D3DVECTOR *vtx;
	WORD *idx;
	D3D11_BUFFER_DESC bdesc;
	D3D11_SUBRESOURCE_DATA sdata;
	
	for( i = 0; i < nshmesh; i++ ) {
		vtx = new D3DVECTOR [eg[i].nvtx];
		idx = new WORD [eg[i].nidx];

		shadow_mesh[i].nidx = 0;
		shadow_mesh[i].nvtx = 0;
		shadow_mesh[i].ecorr = (eg[i].bin - 0.5)*d_ecorr;

		for( j = 0; j < eg[i].nmesh; j++ ) {
			MESHHANDLE mesh = eg[i].mesh[j];
			ngrp = oapiMeshGroupCount( mesh );

			for( k = 0; k < ngrp; k++ ) {
				MESHGROUP *grp = oapiMeshGroup( mesh, k );
			//	if( grp->UsrFlag & 1 )	//"no shadows" flag
			//		continue;

				D3DVECTOR *vtgt = vtx + shadow_mesh[i].nvtx;
				WORD *itgt = idx + shadow_mesh[i].nidx;

				NTVERTEX *vsrc = grp->Vtx;
				WORD *isrc = grp->Idx;

				WORD iofs = (WORD)shadow_mesh[i].nvtx;
				for( m = 0; m < grp->nVtx; m++ ) {
					vtgt[m].x = vsrc[m].x;
					vtgt[m].y = vsrc[m].y;
					vtgt[m].z = vsrc[m].z;
				}

				for( m = 0; m < grp->nIdx; m++ )
					*itgt++ = *isrc++ + iofs;

				shadow_mesh[i].nvtx += grp->nVtx;
				shadow_mesh[i].nidx += grp->nIdx;
			}
		}
		
		ZeroMemory( &bdesc, sizeof(bdesc) );
		bdesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		bdesc.ByteWidth = 12*shadow_mesh[i].nvtx;
		bdesc.CPUAccessFlags = 0;
		bdesc.MiscFlags = 0;
		bdesc.StructureByteStride = 0;
		bdesc.Usage = D3D11_USAGE_IMMUTABLE;

		ZeroMemory( &sdata, sizeof(sdata) );
		sdata.pSysMem = vtx;

		HR( Dev->CreateBuffer( &bdesc, &sdata, &shadow_mesh[i].VB ) );

		ZeroMemory( &bdesc, sizeof(bdesc) );
		bdesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
		bdesc.ByteWidth = 2*shadow_mesh[i].nidx;
		bdesc.CPUAccessFlags = 0;
		bdesc.MiscFlags = 0;
		bdesc.StructureByteStride = 0;
		bdesc.Usage = D3D11_USAGE_IMMUTABLE;

		ZeroMemory( &sdata, sizeof(sdata) );
		sdata.pSysMem = idx;

		HR( Dev->CreateBuffer( &bdesc, &sdata, &shadow_mesh[i].IB ) );

		delete [ ] vtx;
		delete [ ] idx;
	}

	for( i = 0; i < nshmesh; i++ )
		delete [ ] eg[i].mesh;
	delete [ ] eg;
}

void vBase::UpdateRunningLightsSystem(RunningLightsSystem& lightsSystem, double timeInterval)
{
	auto currentDisabledGroupIndex = lightsSystem.disabledGroupIndex;
	auto size = lightsSystem.lightGroups.size();
	if (currentDisabledGroupIndex < size)
	{
		BaseLightsGroupVector& group = lightsSystem.lightGroups[currentDisabledGroupIndex];
		for(decltype(size) i = 0; i < group.size(); i++)
		{
			BaseLight& light = m_baseLights[group[i]];
			light.forceDisable = false;
		}
	}
	currentDisabledGroupIndex += (UINT)(timeInterval / RunningLightsInterval);
	while (currentDisabledGroupIndex >= size)
	{
		currentDisabledGroupIndex -= size;
	}
	{
		BaseLightsGroupVector& group = lightsSystem.lightGroups[currentDisabledGroupIndex];
		for(decltype(size) i = 0; i < group.size(); i++)
		{
			BaseLight& light = m_baseLights[group[i]];
			light.forceDisable = true;
		}
	}
	lightsSystem.disabledGroupIndex = currentDisabledGroupIndex;
}

void vBase::Update() {
	static double csun_lights = RAD;

	vObject::SaveData();
	if( !active )	return;

	vObject::Update();

	//oapi functions:
	oapiGetGlobalPos( planet, &RP.pp );		// planet global pos
	oapiGetGlobalPos( obj, &RP.sd );		// base global pos
	oapiGetRotationMatrix( obj, &RP.vR );

	static double nxtsimt = 0.0;
	double simt = oapiGetSimTime();
	auto timeInterval = simt - m_previousSimTime;
	if (timeInterval > RunningLightsInterval)
	{
		auto size = m_runningLightsSystems.size();
		for(decltype(size) i = 0; i < size; i++)
		{
			RunningLightsSystem& lightsSystem = m_runningLightsSystems[i];
			UpdateRunningLightsSystem(lightsSystem, timeInterval);
		}
		m_previousSimTime = simt;
	}
	if( simt > nxtsimt ) {
		SunLight = D3D11Mesh::WhiteSunLight;

		VECTOR3
			S = pSun - GPos,
			P = unit( GPos - PLN->GPos );

		float s = (float)length( S );
		float
			rs = float(SunSize)/s,
			h = float( dotp( S, P ) )/s,
			d = 0.173f,
			ae = 0.242f,
			aq = 0.342f,
			amb0 = 0.0f,
			disp = 0.0f,
			amb = 0.0f;

		const ATMCONST *atmc = PLN->atmc;

		if( atmc ) {			
			amb0 = min( 0.7f, log( float( atmc->rho0 ) + 1.0f )*0.4f );
			disp = max( 0.02f, min( 0.9f, log( float( atmc->rho0 ) + 1.0f ) ) );
		}

		D3DXVECTOR3	_one = D3DXVECTOR3( 1, 1, 1 );
		D3DXVECTOR3
			lcol,
			r0 = _one - D3DXVECTOR3( 0.4f, 0.65f, 1.0f )*disp;

		if( atmc ) {
			lcol = ( r0 + ( _one - r0 )*saturate(h/d))*saturate((h+rs)/(2.0f*rs));
			amb = saturate( (h+ae)/aq );
			amb = saturate( max( amb0*amb - 0.05f, fAmbient ) );
			lcol *= 1.0f - amb*0.5f;		// reduce direct light component to avoid overexposure
		}
		else {
			lcol = r0 * saturate( (h+rs)/(2.0f*rs) ); 
			amb  = fAmbient;
			lcol *= 1.0f - amb*0.5f;
		}

		SunLight.ambient = D3DXCOLOR( amb, amb, amb, 1.0f );
		SunLight.diffuse = SunLight.specular = D3DXCOLOR( lcol.x, lcol.y, lcol.z, 1.0f );
		SunLight.SunDir = PLN->AP.SunDir;

		nxtsimt = simt + 0.5f;

		VECTOR3 sdir, pos = GPos;
		normalise( pos );
		sdir = tmul( mROT, -pos );
		double csun = sdir.y;
		bool night = csun < csun_lights;
		if( p_night != night ) {
			DWORD j;
			for( j = 0; j < nstructs_bs; j++ )
				structs_bs[j]->SetTextureMix( 1, night ? 1.0f : 0.0f );
			for( j = 0; j < nstructs_as; j++ )
				structs_as[j]->SetTextureMix( 1, night ? 1.0f : 0.0f );
			p_night = night;
		}
	}
}

void vBase::RenderSurface() {
	if( !active )	return;

	if( tilemesh ) {
		auto type = NO_ATMOSPHERE;
		if (PLN->AtmM)
		{
			type = PLN->AtmM->GetType();
		}
		switch(type)
		{
			case NO_ATMOSPHERE:
				break;
		}
		iCtx->PSSetShader(m_pixelShader, nullptr, 0);
		memcpy( &D3D11Mesh::PSCB_per_object.Sun, &SunLight, sizeof(Sun_spec) );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );

		tilemesh->RenderBaseTiles( this, &mWorld );
	}
}

void vBase::RenderStructuresBS() 
{
	if( !active )	return;

	if( nstructs_bs ) 
	{
		memcpy( &D3D11Mesh::VSCB_per_object.Sun, &SunLight, 64 );
		D3D11Mesh::VSCB_per_object.VP = *SC->GetVP();
		D3D11Mesh::VSCB_per_object.ActiveLightsCount = 0;
		iCtx->UpdateSubresource( D3D11Mesh::cb_VS_per_object, 0, NULL, &D3D11Mesh::VSCB_per_object, 0, 0 );

		memcpy( &D3D11Mesh::PSCB_per_object.Sun, &SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );

		for( UINT j = 0; j < nstructs_bs; j++ )
			structs_bs[j]->Render( this, &mWorld );
	}
}

void vBase::RenderStructuresAS() 
{
	if( !active )	return;

	if( nstructs_as ) 
	{
		memcpy( &D3D11Mesh::VSCB_per_object.Sun, &SunLight, 64 );
		D3D11Mesh::VSCB_per_object.VP = *SC->GetVP();
		D3D11Mesh::VSCB_per_object.ActiveLightsCount = 0;
		iCtx->UpdateSubresource( D3D11Mesh::cb_VS_per_object, 0, NULL, &D3D11Mesh::VSCB_per_object, 0, 0 );

		memcpy( &D3D11Mesh::PSCB_per_object.Sun, &SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );
		
		for( UINT j = 0; j < nstructs_as; j++ )
			structs_as[j]->Render( this, &mWorld );
	}
}

void vBase::InitRenderShadows() {
	iCtx->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
	iCtx->IASetInputLayout( D3D11Mesh::IL_D3DXVECTOR3 );
	iCtx->VSSetShader( D3D11Mesh::VS_Shadow, NULL, NULL );
	iCtx->VSSetConstantBuffers( 0, 1, &cb_D3DXMATRIX_x1 );
	iCtx->PSSetShader( D3D11Mesh::PS_Shadow, NULL, NULL );
	iCtx->PSSetConstantBuffers( 0, 1, &cb_D3DXVECTOR4 );
	iCtx->OMSetDepthStencilState( D3D11Mesh::DSS_Shadow, 1 );
	iCtx->RSSetState( RS_CullBack_Solid );
}

void vBase::RenderShadows() 
{
	//same for vessel shadows
	if( !active )	return;

	D3DXMATRIX mProjWorldOfs = mWorld;
	for( DWORD j = 0; j < nstructs_bs; j++ )
		structs_bs[j]->RenderShadows( &mProjWorldOfs );
	for( DWORD j = 0; j < nstructs_as; j++ )
		structs_as[j]->RenderShadows( &mProjWorldOfs );

/*
	if( !nshmesh )	return;

	static const double shadow_elev_limit = 0.07;

	double d, csun, nr0;
	VECTOR3 pvr;

	pvr = RP.sd - RP.pp;				// planet-relative base position
	d = length( pvr );					// planet radius at base location
	normalise( RP.sd );					// shadow projection direction

	double fac1 = dotp( RP.sd, pvr );
	if( fac1 > 0.0 )					// base is on planet night-side
		return;
	csun = -fac1/d;						// sun elevation above horizon
	if( csun < shadow_elev_limit )		// sun too low to cast shadow
		return;

	VECTOR3
		sdv = tmul( RP.vR, RP.sd ),		// projection direction in base frame
		hnp = pvr;
	normalise( hnp );
	VECTOR3 hn = tmul( RP.vR, hnp );	// horizon normal in vessel frame

	// perform projections
	double nd = dotp( hn, sdv );
	VECTOR3 sdvs = sdv/nd;
	if( !sdvs.y )						// required for plane offset correction
		return;

	DWORD i;
	const UINT
		D3DVECTORStride = 12,
		VBOffset = 0;

	// build shadow projection matrix
	D3DXMATRIX mProj, mProjWorld, mProjWorldShift;
	mProj._11 = (float)(1.0 - sdvs.x*hn.x);
	mProj._12 = (float)(    - sdvs.y*hn.x);
	mProj._13 = (float)(    - sdvs.z*hn.x);
	mProj._14 = 0;
	mProj._21 = (float)(    - sdvs.x*hn.y);
	mProj._22 = (float)(1.0 - sdvs.y*hn.y);
	mProj._23 = (float)(    - sdvs.z*hn.y);
	mProj._24 = 0;
	mProj._31 = (float)(    - sdvs.x*hn.z);
	mProj._32 = (float)(    - sdvs.y*hn.z);
	mProj._33 = (float)(1.0 - sdvs.z*hn.z);
	mProj._34 = 0;
	mProj._41 = 0;
	mProj._42 = 0;
	mProj._43 = 0;
	mProj._44 = 1.0f;

	D3DXMatrixMultiply( &mProjWorld, &mProj, &mWorld );
	mProjWorldShift = mProjWorld;
//	memcpy( );	later
	
	float scale = float( min( 1, (csun - 0.07)/0.015) );
	if( scale < 1 )
		alpha *= scale;

	D3DXVECTOR4 v( alpha, 0.5f, 0.5f, 0.5f );
	iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, &v, 0, 0 );

	for( i = 0; i < nshmesh; i++ ) {

		// add shadow plane offset to transformation
		nr0 = shadow_mesh[i].ecorr/sdvs.y;
		mProjWorldShift._41 = mProjWorld._41 + (float)( nr0*( sdvs.x*mWorld._11 + sdvs.y*mWorld._21 + sdvs.z*mWorld._31 ) );
		mProjWorldShift._42 = mProjWorld._42 + (float)( nr0*( sdvs.x*mWorld._12 + sdvs.y*mWorld._22 + sdvs.z*mWorld._32 ) );
		mProjWorldShift._43 = mProjWorld._43 + (float)( nr0*( sdvs.x*mWorld._13 + sdvs.y*mWorld._23 + sdvs.z*mWorld._33 ) );

		mProjWorldShift = mWorld;
		mProjWorldShift *= *SC->GetVP();

		iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, &mProjWorldShift, 0, 0 );

		iCtx->IASetVertexBuffers( 0, 1, &shadow_mesh[i].VB, &D3DVECTORStride, &VBOffset );
		iCtx->IASetIndexBuffer( shadow_mesh[i].IB, DXGI_FORMAT_R16_UINT, 0 );

		iCtx->DrawIndexed( shadow_mesh[i].nidx, 0, 0 );
	}*/
}

//base craters:
double vBase::GetBaseMeshRadius() {
	double maxbs;

	//bounding box of all tilemeshes and base structures:
	D3DXVECTOR3 AABB[2], BB[2];
	memset( AABB, 0, 24 );
	if( tilemesh )	tilemesh->GetBoundingBox( AABB );
	for( UINT j = 0; j < nstructs_bs; j++ ) {
		structs_bs[j]->GetBoundingBox( BB );

		if( BB[0].x < AABB[0].x )	AABB[0].x = BB[0].x;
		if( BB[0].y < AABB[0].y )	AABB[0].y = BB[0].y;
		if( BB[0].z < AABB[0].z )	AABB[0].z = BB[0].z;

		if( BB[1].x > AABB[1].x )	AABB[1].x = BB[1].x;
		if( BB[1].y > AABB[1].y )	AABB[1].y = BB[1].y;
		if( BB[1].z > AABB[1].z )	AABB[1].z = BB[1].z;
	}
	for( UINT j = 0; j < nstructs_as; j++ ) {
		structs_as[j]->GetBoundingBox( BB );

		if( BB[0].x < AABB[0].x )	AABB[0].x = BB[0].x;
		if( BB[0].y < AABB[0].y )	AABB[0].y = BB[0].y;
		if( BB[0].z < AABB[0].z )	AABB[0].z = BB[0].z;

		if( BB[1].x > AABB[1].x )	AABB[1].x = BB[1].x;
		if( BB[1].y > AABB[1].y )	AABB[1].y = BB[1].y;
		if( BB[1].z > AABB[1].z )	AABB[1].z = BB[1].z;
	}

	AABB[1] -= AABB[0];
	maxbs = D3DXVec3Length( &AABB[1] )/2.0f;
	return maxbs;
}
